home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / admin / quota < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  73.3 KB  |  2,037 lines

  1. #!/usr/local/bin/gawk -f
  2. # *** NOTE!!! ***
  3. # This is one of three programs you will need to implement advisory quotas.
  4. # There is also a related utility to actually pare down users' usage.
  5. # The complete set consists of quota, usage, pareacct, and gawk (quota is
  6. # written in gawk; it requires gawk to interpret/run it).
  7. # The URLs to retrieve these are:
  8. # ftp://ftp.armory.com/pub/admin/quota
  9. # ftp://ftp.armory.com/pub/admin/usage
  10. # ftp://ftp.armory.com/pub/admin/pareacct
  11. # ftp://ftp.armory.com/pub/scobins/gawk (binary for SCO UNIX 3.2v5)
  12.  
  13. # Make line 1 of this program point to wherever gawk is located on your system.
  14. # This program was written for gawk 2.15.5 and later, and is liable to fail if
  15. # used with earlier versions, since 2.15.5 had several important bugfixes.
  16. # If you are running a version of UNIX that does not have hashbang (like
  17. # SCO UNIX prior to 3.2v4), or on which it is turned off, you can invoke the
  18. # script by putting it into a file named e.g. /usr/lib/quota.awk and running
  19. # it as follows:
  20. # gawk -f /usr/lib/quota.awk [arguments]
  21. # If you do not have gawk, replace the first line with this:
  22. #!/usr/bin/awk -f
  23. # You will then have to give all options to the program with '+' instead of '-'
  24. # since it is a feature of gawk that '-' options can be used.  You will also
  25. # have to do this if using an older version of gawk.   e.g. you would do
  26. # 'quota +d /u' instead of 'quota -d /u'.  You can also pass the program
  27. # file to awk explicitly (without using #!) as in the example for gawk.
  28. # If you are using the #! method of invoking awk or gawk on this program and
  29. # get the error "quota: not found" even when you give an explicit path to the
  30. # quota program, it probably means that the path to gawk that comes after #!
  31. # is incorrect.
  32. # Note: I have only done superficial testing of this program to see if it 
  33. # works with awk.
  34. # A binary for gawk 2.15.6 compiled for SCO UNIX can be found on ftp.armory.com
  35. # in pub/scobins/gawk.  The source, ready to compile (with gcc) for SCO and
  36. # including man pages, etc. is also there in pub/source/gawk-2.15.6.tar.Z
  37.  
  38. # @(#) quota.gawk 2.6 97/07/14
  39. # 94/03/03 John H. DuBois III (john@armory.com)
  40. # 94/03/06 Fixed Usage var name conflict so gawk no longer core dumps.
  41. # 94/03/14 Avoid gawk bug by only putting indexes in Usage[] for explicitly
  42. #          named users and those using more than their quota.
  43. # 94/03/18 Added q option
  44. # 94/03/20 Added usage-last-checked field to output
  45. # 94/03/25 Added total overusage display
  46. # 94/04/01 Fixed d option
  47. # 94/07/11 Read date from usage file
  48. # 94/07/14 Added p option.
  49. # 94/07/15 Added code to deal with warning level lower than real-quota.
  50. # 94/07/20 No quota if user not listed and no DEFAULT.
  51. #          Let aliases be given for quotas.
  52. #          Let more than one filesystem be named with -d.
  53. # 95/01/07 Indicate error if bad user name given.
  54. # 95/01/14 Let '-' indicate no quota in cases when DEFAULT is set.
  55. # 95/02/01 Added t and s options.
  56. # 95/05/10 Changed meaning of p option so that it doesn't turn on o option too,
  57. #          so it can be used for a general report.
  58. # 95/05/12 Rewrote report printer.  Added [ru] options.
  59. # 95/07/02 Began adding code for inode quotas
  60. # 95/07/22 Make DEFAULT default to no quota.
  61. # 95/08/03 Fixed spurious complaint re OverUse & Rept not in agreement.
  62. #          Warn if invoking user is root & no quota or usage files are found.
  63. #          Do not report on nonexistant users.
  64. # 95/08/11 Added c option.
  65. # 95/09/12 Fixed name of Quotas file in help, and made -c option explicitly
  66. #          refer to Quotas files.
  67. # 95/12/16 Make sort option work correctly.
  68. # 96/03/11 Better formatting of warning message.
  69. # 96/04/15 Added H option; added overuse amount to -o report.
  70. # 97/03/01 Read config file.
  71. # 97/04/20 Added l option.
  72. # 97/07/14 2.6 Changed -q option to -R; added new q option; added O option.
  73.  
  74. # Lame substitute for real disk quotas.
  75.  
  76. BEGIN {
  77.     Name = "quota"
  78.     rcFile = "/etc/default/" Name
  79.     HUsage = \
  80. "Usage: " Name " [-cCehHropPRstw] [-d<mountdir[,mountdir...]>] [-u<usage>]\n"\
  81. "             [-q<quota[:warn-quota]>] [-O<min-over-use>] [username ...]"
  82.     ARGC = Opts(Name,HUsage,"d:slq:cCheHRwoO>rpPtxu<",0,rcFile,
  83.     "FILESYSTEMS,SORTUSAGE,LASTLOGIN,QUOTA")
  84.     if ("h" in Options) {
  85.     printf \
  86. "%s: Report filesystem usage and quotas.\n"\
  87. "%s\n"\
  88. "%s reports users' disk usage and quotas on filesystems that\n"\
  89. "have disk usage database files (as built by the 'usage' program).\n"\
  90. "Options:\n"\
  91. "Some of the following options can also be set by assigning values to\n"\
  92. "variables in a configuration file named %s.  Variables are\n"\
  93. "assigned to with the syntax:  varname=value  or in the case of flags, by\n"\
  94. "simply putting the indicated variable name in the file without a value.\n"\
  95. "Options given on the command line override assigments in the configuration\n"\
  96. "file.  Flag options set in the configuration file can be turned off on the\n"\
  97. "command line by following them immediately with \"-\", e.g. -v- to turn\n"\
  98. "off the v option in such a way that it cannot be turned on in the config\n"\
  99. "file.  Variable names appear in parentheses in the option descriptions.\n"\
  100. "-t: Print a total line.\n"\
  101. "-H: Do not print column headers.\n"\
  102. "-d<mountdir[,mountdir...]>: Report quotas on the filesystems mounted on\n"\
  103. "    the named directories.  (FILESYSTEMS)\n"\
  104. "-q<quota[:warning-quota]>: Set the quota for all users to <quota>\n"\
  105. "    kilobytes.  If two numbers are given separated by a colon, the second\n"\
  106. "    sets the warning quota; if not, both the quota and warning quota are\n"\
  107. "    set to the single value given.  No quota file is read or needed.  By\n"\
  108. "    default, the quota is applied to all filesystems, which is probably\n"\
  109. "    not the desired behaviour, so -d should generally be used with this\n"\
  110. "    option.  (QUOTA)\n"\
  111. "The rest of the options all change the output format or relate to the\n"\
  112. "options that change the output format:\n"\
  113. "-c: Check the local quota configuration.  All mounted directories are\n"\
  114. "    checked for Quotas and Usage files, etc.  Use this option after\n"\
  115. "    configuring the quota system.\n"\
  116. "-C: Like -c, except that a list of every user who does not have a quota\n"\
  117. "    on the filesystem that their home directory is on is printed.\n"\
  118. "-e: Print a description of the quota configuration file.\n"\
  119. "-h: Print this help.\n"\
  120. "-r: Read a list of user names from the standard input, one per line,\n"\
  121. "    rather than taking them from the command line.\n"\
  122. "-o: Print a report on users who have exceeded their quotas.  The format is\n"\
  123. "    the same as the single-user report, except that each line is prefixed\n"\
  124. "    by the user name.  Output is sorted by total overusage.  -u may be used\n"\
  125. "    with this option to set a minimal usage (not overusage) for a user to\n"\
  126. "    be reported on, but only users who exceed their quota will be listed.\n"\
  127. "-l: Like -o, except that the time of the most recent shell login or\n"\
  128. "    popmail access is included for each user in the report.  If set in a\n"\
  129. "    config file, its effect is to make -o behave like -l.  This option\n"\
  130. "    requires the \"lastlogin\" utility to be available.  (LASTLOGIN)\n"\
  131. "-p: Print a report more suitable for processing by further programs: each\n"\
  132. "    line contains a user name followed by triples of (filesystem, usage,\n"\
  133. "    quota). Each user will be listed on only one line, with all of the\n"\
  134. "    filesystems being reported on on that line.  No headers are printed.\n"\
  135. "    If -o is also given, only those filesystems on which the user is\n"\
  136. "    exceeding quota are listed.\n"\
  137. "-s: Sort output by total usage.  (SORTUSAGE)\n"\
  138. "-u<usage>: Report on users whose usage on a filesystem exceeds <usage>\n"\
  139. "    kilobytes.\n"\
  140. "-O<min-over-use>: Report on users whose usage on a filesystem exceeds\n"\
  141. "    <min-over-use> kilobytes.  Normally used to set a minimum threshold\n"\
  142. "    for the -o and -l reports.\n"\
  143. "-w: Print a warning for any filesystem on which the warning threshold has\n"\
  144. "    been exceeded.  %s -w is typically put in /etc/profile and\n"\
  145. "    /etc/login.  The -d option may be used with this to make the command\n"\
  146. "    run faster by avoiding the need to find mounted filesystems.\n"\
  147. "-R: Use the real quota for the warning threshold (for use with -w).\n",
  148.     Name,HUsage,Name,rcFile,Name
  149.     exit 0
  150.     }
  151.     if ("e" in Options) {
  152.     printf \
  153. "     The quota configuration file for each filesystem is named Quotas and\n"\
  154. "is located in the root directory of the filesystem.  The root directory of\n"\
  155. "a filesystem is the directory that appears at the mount point when the\n"\
  156. "filesystem is mounted; e.g. if a filesystem is mounted on /u, the root\n"\
  157. "directory for the filesystem mounted there is /u.  If a Quotas file does\n"\
  158. "not exist in a filesystem root directory, there are no quotas on that\n"\
  159. "filesystem (unless -q is used, in which case no quota files are searched\n"\
  160. "for or used).  Typically, a Quotas file is created for each filesystem that\n"\
  161. "user accounts are stored on and possibly for other filesystems that users\n"\
  162. "store files on, and \"usage\" is run at regular intervals to keep the\n"\
  163. "Usage file for each of those filesystems up to date.\n"\
  164. "     The Quotas file must be readable by users to be acted on. For\n"\
  165. "non-root users, no warning is produced if there is no Quotas file for a\n"\
  166. "filesystem even if the filesystem was named with the -d option, or if\n"\
  167. "there is a Quotas file and no Usage file or vice versa, to avoid confusing\n"\
  168. "users if a Quotas file is temporarily removed or unreadable.  \n"\
  169. "     File format:\n"\
  170. "username real-quota warning-quota\n"\
  171. "username is a user login, an alias, or the special name DEFAULT which\n"\
  172. "takes effect for all users not explicitly named.  real-quota and\n"\
  173. "warning-quota are either aliases or values given in 1K blocks.  A quota of\n"\
  174. "'-' means the user has no quota.  Any other non-numeric value is taken to\n"\
  175. "be an alias; a quota line must be given for an alias before it can be used.\n"\
  176. "The real-quota is the amount of disk space the user is allowed to use.  It\n"\
  177. "is what usage is checked against when the -o and -p options are used, and\n"\
  178. "is what the user is informed the quota is.  The warning-quota is used to\n"\
  179. "give the users a bit of leeway before they are warned.  When -w is used,\n"\
  180. "no warning is printed unless the warning-quota has been exceeded (but in\n"\
  181. "the warning message, if any, the real-quota is still used).  If a\n"\
  182. "warning-quota is given for a user, it should be equal to or greater than\n"\
  183. "the real-quota for the user.  If -q is used with -w, the real-quota is\n"\
  184. "used instead.  If no warning-quota is given in the Quotas file, real-quota\n"\
  185. "is always used.  Blank lines and lines beginning with # are comments and\n"\
  186. "are ignored.  If there is no DEFAULT line, users who are not listed have\n"\
  187. "no quota on the given filesystem and no line will be printed for it when\n"\
  188. "they run \"%s\".\n"\
  189. "Example Quotas file:\n"\
  190. "# User    Quota    Warn level (both in K)\n"\
  191. "DEFAULT    5000    7000\n"\
  192. "# armorites\n"\
  193. "Arm 40000\n"\
  194. "spcecdt    70000\n"\
  195. "zap    Arm\n"\
  196. "crisper    Arm\n"\
  197. "# others\n"\
  198. "falcon    15000\n"\
  199. "taz    15000\n",Name
  200.     exit 0
  201.     }
  202.     Debug = "x" in Options
  203.     Parseable = "p" in Options
  204.     rStdin = "r" in Options
  205.     Check = "c" in Options || "C" in Options
  206.     Warn = "w" in Options    # Print a warning message.
  207.     DoTotal = "t" in Options
  208.     if ("O" in Options)
  209.     minOverUse = Options["O"]
  210.     if ("q" in Options) {
  211.     if ((nElem = split(Options["q"],Elem,":")) > 2) {
  212.         printf "%s: Bad value given with -q option: too many fields.\n",
  213.         Name > "/dev/stderr"
  214.         exit(1)
  215.     }
  216.     if (Elem[1] !~ /^[0-9]+$/ || Elem[1]+0 == 0 || nElem == 2 && \
  217.     (Elem[2] !~ /^[0-9]+$/ || Elem[2]+0 == 0)) {
  218.         printf "%s: Values given with -q must be positive integers.\n",
  219.         Name > "/dev/stderr"
  220.         exit(1)
  221.     }
  222.     globalQuota = Elem[1]
  223.     globalWarnQuota = (nElem == 2) ? Elem[2] : globalQuota
  224.     }
  225.     Headers = !("H" in Options || Parseable || Warn)
  226.     # OverUse is set if it was specified or if -l given on command line
  227.     OverUse = "o" in Options || OptsGiven(Options,"l",1,0,0)
  228.     LastLogin = OverUse && "l" in Options
  229.  
  230.     # Trust USER var to avoid running id, since the files are readable anyway.
  231.     if ("USER" in ENVIRON) {
  232.     user = ENVIRON["USER"]
  233.     uid = (user != "root")    # set to 0 if user is root
  234.     }
  235.     else {
  236.     id(IDs)
  237.     uid = IDs["uid"]
  238.     user = IDs["user"]
  239.     UIDs[user] = uid
  240.     }
  241.     RealUsers[user]
  242.     if (Check)
  243.     uid = 0        # Do all checks
  244.  
  245.     tfTime = "%a %b %d %H:%M"    # 24 hour time format
  246.  
  247.     # Build list of filesystems to check.
  248.     # Let -d "" override FILESYSTEMS
  249.     if (!Check && (DirsGiven = ("d" in Options)) && Options["d"] != "")
  250.     split(Options["d"],Mounted,",")
  251.     else
  252.     if (GetMount(Mounted)) {
  253.         printf "%s: Could not get mount table.\n",Name > "/dev/stderr"
  254.         exit(1)
  255.     }
  256.  
  257.     NamesGiven = (OverUse || uid == 0) && (ARGC > 1 || rStdin)
  258.     # If nonroot user or no user names given, report on current user.
  259.     # The output will tell who it's really for.
  260.     if (!NamesGiven) {
  261.     ARGV[1] = user
  262.     ARGC = 2
  263.     }
  264.     # If -r given, add user names read from stdin to list of users to report on
  265.     if ((OverUse || uid == 0) && rStdin)
  266.     while ((getline < "/dev/stdin") == 1)
  267.         ARGV[ARGC++] = $0
  268.  
  269.     # Build set of users to gather data on (but not neccessarily report on).
  270.     for (UserNum = 1; UserNum < ARGC; UserNum++)
  271.     Users[ARGV[UserNum]]
  272.     Users["DEFAULT"]
  273.  
  274.     # If checking, get usage for all users
  275.     MinUsage = Check ? 0 : ("u" in Options ? Options["u"] : -1)
  276.     # Gather data on specified users, or on overuse.
  277.     # Also, if overuse or -u report is requested, add all users matching given
  278.     # usage criteria to Users[]
  279.     for (Device in Mounted) {
  280.     MountDir = Mounted[Device]
  281.     gotQuotas = 0
  282.     if (globalQuota) {
  283.         Quotas[MountDir,"DEFAULT"] = globalQuota
  284.         WarnQuot[MountDir,"DEFAULT"] = globalWarnQuota
  285.         gotQuotas = 1
  286.     }
  287.     else
  288.         if (GetQuotas(MountDir,Quotas,WarnQuot,"R" in Options)) {
  289.         if (DirsGiven && !uid)
  290.             printf "%s: Warning: No Quotas file found on %s\n",
  291.             Name,MountDir > "/dev/stderr"
  292.         }
  293.         else
  294.         gotQuotas = 1
  295.     if (gotQuotas) {
  296.         FoundQuota = 1
  297.         if (GetUsage(MountDir,Users,WarnQuot,IWarnQuot,OverUse,Usage,
  298.         Inodes,MinUsage,-1,NamesGiven) && !uid)
  299.         printf "%s: Warning: No Usage file found on %s\n",
  300.         Name,MountDir > "/dev/stderr"
  301.         QuotaDev[Device]
  302.     }
  303.     }
  304.     if (!uid && !FoundQuota)
  305.     printf "%s: No Quotas files found.\n",Name > "/dev/stderr"
  306.  
  307.     # If an overuse or over-specified-value report was requested,
  308.     # users matching given criteria are added to Users[];
  309.     # now add them to the list of users to be checked/reported on,
  310.     # with ARGC/ARGV the same as they are normally set up
  311.     # (1st param is ARGV[1], ARGC = number of params + 1)
  312.     if (!NamesGiven && (OverUse || "u" in Options)) {
  313.     ARGC = 1
  314.     for (User in Users)
  315.         ARGV[ARGC++] = User
  316.     }
  317.     Sort = "s" in Options || OverUse || MinUsage > -1
  318.  
  319.     # Report on each user in ARGV[]
  320.     # Do this even if doing Check, as an extra test; nothing will be printed
  321.     # except on error because Check sets MinUsage which sets Sort.
  322.     for (UserNum = 1; UserNum < ARGC; UserNum++) {
  323.     CUser = ARGV[UserNum]
  324.     if (Debug)
  325.         print "Processing user: " CUser > "/dev/stderr"
  326.     OverSum += ProcUser(ARGV[UserNum],UIDs,QuotaDev,Mounted,Usage,Quotas,
  327.     WarnQuot,Warn,OverUse,Parseable,DoTotal,Sort,Reports,ReptUsage,MinUsage,
  328.     minOverUse)
  329.     }
  330.  
  331.     if (Check) {
  332.     DoCheck(Mounted,QuotaDev,Quotas,Usage,"C" in Options)
  333.     exit(0)
  334.     }
  335.     if (Sort) {
  336.     if ("Header" in Reports) {
  337.         if (LastLogin)
  338.         sub("\n","  last login\n",Reports["Header"])
  339.         printf "%s",Reports["Header"]
  340.     }
  341.     # If multiline report, print newline between reports for each user.
  342.     nlFmt = (OverUse || MinUsage > -1 || Parseable) ? "%s" : "\n%s"
  343.     Num = qsortArbIndByValue(ReptUsage,k)
  344.     if (Num > 0) {
  345.         if (LastLogin) {
  346.         Cmd = "lastlogin -zlpES"
  347.         for (User in Reports)
  348.             Cmd = Cmd " " User
  349.         Cmd = Cmd " 2>/dev/null"
  350.         while (Cmd | getline) {
  351.             User = $1
  352.             if (!(User in Reports))
  353.             printf "%s: Strange line returned by lastlogin: %s\n",
  354.             Name,$0 > "/dev/stderr"
  355.             else {
  356.             $1 = ""
  357.             sub("\n"," " $0 "\n",Reports[User])
  358.             }
  359.         }
  360.         close(Cmd)
  361.         sub("\n","  last login\n",Reports["Header"])
  362.         }
  363.         printf "%s",Reports[k[1]]
  364.         for (i = 2; i <= Num; i++)
  365.         printf nlFmt,Reports[k[i]]
  366.     }
  367.     }
  368.     
  369.     if (DoTotal && ARGC > 2)
  370.     printf "\nTotal usage: %d\n",AllUsage
  371.     if (OverUse && Headers)
  372.     printf "Total overusage: %d\n",OverSum
  373. }
  374.  
  375. # Tells what filesystem File is on.
  376. # MountDirs[] is a list of mount directories, indexed by device.  It
  377. # is used only the first time this function is called.
  378. # This function will only work with absolute paths that do not include .,
  379. # .., and do not have more than one / after each directory.
  380. function FileDevice(File,MountDirs,  i,dev,dir) {
  381.     if (IsEmpty(_SortedMountDirs)) {
  382.     for (dev in MountDirs) {
  383.         dir = MountDirs[dev]
  384.         if (dir !~ "/$")    # make the comparisons later easier
  385.         dir = dir "/"
  386.         _SortedMountDirs[++i] = dir
  387.     }
  388.     qsortNumIndByValue(_SortedMountDirs,1,i)
  389.     _NumMountDirs = i
  390.     }
  391.     File = File "/"    # In case it is one of the mount dirs
  392.     for (i = _NumMountDirs; i >= 1; i--)
  393.     if (index(File,dir = _SortedMountDirs[i]) == 1) {
  394.         if (dir != "/")
  395.         sub("/$","",dir)
  396.         return dir
  397.     }
  398. }
  399.  
  400. # Put a list of login shells (from /etc/shells) into set LoginShells[].
  401. # Returns -1 if /etc/shells could not be read, else the number of shells found.
  402. function ReadShells(LoginShells,  ret,Num,Line) {
  403.     while (ret = ((getline Line < "/etc/shells") == 1))
  404.     if (Line ~ "^/") {
  405.         Num++
  406.         sub(/[ \t]+/,"",Line)
  407.         LoginShells[Line]
  408.     }
  409.     close("/etc/shells")
  410.     return ret ? -1 : Num
  411. }
  412.  
  413. function DoCheck(Mounted,QuotaDev,Quotas,Usage,ListUsers,
  414. Device,NoQuota,LoginShells,Dir,RealUsers,Values,WarnQuot,NumNoQuota,Name) {
  415.     print "Quotas exist (Quotas file found) for these filesystems:"
  416.     for (Device in Mounted)
  417.     if (Device in QuotaDev)
  418.         printf "%s (%s)\n",Mounted[Device],Device
  419.     else
  420.         NoQuota[Device]
  421.     print ""
  422.     print "No quotas exist (no Quotas file found) for these filesystems:"
  423.     for (Device in NoQuota)
  424.     printf "%s (%s)\n",Mounted[Device],Device
  425.     print ""
  426.     if (ReadShells(LoginShells) == -1) {
  427.     printf "%s: Could not read /etc/shells; exiting.\n",Name > "/dev/stderr"
  428.     exit 1
  429.     }
  430.     while (getpwent(PWent))
  431.     if (PWent[PW_SHELL] in LoginShells && PWent[PW_UID] >= 200) {
  432.         Dir = FileDevice(PWent[PW_HOME],Mounted)
  433.         Name = PWent[PW_NAME]
  434.         if (!GetQuota(Values,Name,Dir,Quotas,WarnQuot,RealUsers)) {
  435.         NumNoQuota[Dir]++
  436.         if (Debug)
  437.             printf "%s has no quota on %s\n",Name,Dir
  438.         if (ListUsers)
  439.             NoQuota[Dir] = NoQuota[Dir] Name " "
  440.         }
  441.     }
  442.     for (Dir in NumNoQuota) {
  443.     printf \
  444.     "%d user(s) whose home directories are under %s\n"\
  445.     "have no quota on that filesystem%s\n",NumNoQuota[Dir],Dir,
  446.     ListUsers ? ":" : "."
  447.     if (ListUsers)
  448.         print NoQuota[Dir] "\n"
  449.     }
  450. }
  451.  
  452. # ProcUser: print usage report.
  453. # Returns: overusage.
  454. # Uses globals: RealUsers[]
  455. # Sets/uses globals: AllUsage, Printed
  456.  
  457. # If Sort is true, report lines are returned in Reports[] with the user name
  458. # as index.
  459. # The user's overusage or total usage is stored in ReptUsage[user].
  460. # If Sort is not true, messages are printed directly.
  461. # If MinUsage is not -1, it sets a minimum per-filesystem usage below which
  462. # users are not reported on.
  463. # Headers must be global so it can be turned off after 1st header by OverUse
  464.  
  465. function ProcUser(User,UIDs,QuotaDev,Mounted,Usage,Quotas,WarnQuot,Warn,
  466. OverUse,Parseable,DoTotal,Sort,Reports,ReptUsage,MinUsage,minOverUse,
  467. Format,Device,MountDir,Index,DUsage,WarnLevel,Quota,ChkTime,OverAmt,
  468. TotUsage,TotQuota,Rept,Values,ret,Header) {
  469.     # DidName is used by the Parseable option to record whether the name at
  470.     #   the start of the line has been recorded yet, and to indicate that some
  471.     #   data is being returned for this user.
  472.     # Rept is used to build up the report to be printed or stored in Reports[]
  473.  
  474.     # Make usage report similar to BSD quota report
  475.     # filesys usage quota [overuse] last-usage-check
  476.     Format = "%-10s %6s %6s%" (OverUse ? 8 : ".0") "s  %s\n"
  477.  
  478.     # If generating a per-user multiline report, make header etc.
  479.     if (!OverUse && MinUsage == -1 && !Parseable) {
  480.     
  481.     if (Printed && !Sort) # Don't print separator if we aren't printing.
  482.         # If a user report has been already been printed, print a newline
  483.         # before continuing so that user reports will be separated by
  484.         # blank lines.
  485.         print ""
  486.     else
  487.         Printed = 1
  488.  
  489.     if (!Warn) {
  490.         if (User in UIDs)
  491.         Header = \
  492.         Header sprintf("Disk quotas for %s (uid %d):\n",User,UIDs[User])
  493.         else
  494.         Header = Header sprintf("Disk quotas for %s:\n",User)
  495.     }
  496.     }
  497.     for (Device in QuotaDev) {
  498.     MountDir = Mounted[Device]
  499.     Index = MountDir SUBSEP User
  500.     if (Index in Usage)
  501.         DUsage = Usage[Index] + 0
  502.     else
  503.         DUsage = 0
  504.     ChkTime = Usage[MountDir]
  505.  
  506.     if ((ret = GetQuota(Values,User,MountDir,Quotas,WarnQuot,RealUsers)) \
  507.     != 1)
  508.         if (!ret)    # no quota on this device
  509.         continue
  510.         else    # bad user
  511.         return 0
  512.     Quota = Values["quota"]
  513.     WarnLevel = Values["warn"] + 0
  514.  
  515.     # If we are doing an overuse warning or report and the user's usage
  516.     # is below the warning level, OR there is a minimum usage set (with
  517.     # -u) and the user's usage is below it, OR there is a minimum overusage
  518.     # set and the user's usage is below it, skip the reporting/recording.
  519.     if ((Warn || OverUse) && (DUsage <= WarnLevel) || \
  520.     MinUsage > -1 && DUsage < MinUsage || \
  521.     minOverUse && (DUsage - WarnLevel) < minOverUse)
  522.         continue
  523.     Rept = Rept fsMessage(Warn,Parseable,OverUse || MinUsage > -1,OverUse,
  524.     DUsage,Quota,MountDir,ChkTime,User,Format)
  525.     if (Debug)
  526.         print "Report is now:\n" Rept > "/dev/stderr"
  527.  
  528.     TotUsage += DUsage
  529.     TotQuota += Quota
  530.  
  531.     if ((DUsage - Quota) > 0)
  532.         OverAmt += DUsage - Quota
  533.  
  534.     }
  535.     if (Debug)
  536.     printf "OverAmt=%d\n",OverAmt
  537.     if (Rept == "" && OverAmt > 0)
  538.     printf "%s: Report and OverAmt are not in agreement for user %s!\n",
  539.     Name,User > "/dev/stderr"
  540.     # If anything was recorded for this user, need username & trailing newline
  541.     if (Parseable)
  542.     Rept = User Rept "\n"
  543.  
  544.     # If doing OverUse report, we only want a global total.
  545.     if (!OverUse && MinUsage == -1 && DoTotal)
  546.     Rept = Rept sprintf(Format,"Total",TotUsage,TotQuota,"",
  547.     FormatTime(tfTime,ChkTime))
  548.     AllUsage += TotUsage
  549.     if (Rept != "") {
  550.     if (Headers) {    # If function was called to build headers...
  551.         Header = Header sprintf(Format,"filesys","usage","quota","overuse",
  552.         "last usage check")
  553.     }
  554.     if (Sort) {
  555.         if (Headers)
  556.         if (OverUse)
  557.             Reports["Header"] = sprintf("%8s ","user") Header
  558.         else
  559.             Rept = Header Rept
  560.         ReptUsage[User] = OverUse ? OverAmt : TotUsage
  561.         Reports[User] = Rept
  562.     }
  563.     else
  564.         printf "%s",Header Rept
  565.     if (OverUse)    # Only print one header
  566.         Headers = 0
  567.     }
  568.     return OverAmt
  569. }
  570.  
  571. function FormatTime(Format,Time) {
  572.     if (Time + 0 != 0)    # epoch time; format it
  573.     return strftime(Format,Time)
  574.     else    # preformatted
  575.     return Time
  576. }
  577.  
  578. # Warn, Parseable, and IncUser set the type of report to be generated.
  579. # If Warn is true, a warning message is returned.
  580. # If Parseable is true, the message returned is a triple to be included in a
  581. # single-line message.  It is not newline-terminated.
  582. # If neither Warn nor Parseable are true, a BSD-style usage line is returned;
  583. # if IncUser is true, this line is preceded by the user name.
  584. # Note that the 'Warn' message will only be meaningful if the
  585. # user has exceeded quota.
  586. # DUsage, Quota, MountDir, ChkTime, and User are used in the returned messages.
  587. # Format is the printf format to use for BSD style output.
  588. function fsMessage(Warn,Parseable,IncUser,OverUse,DUsage,Quota,MountDir,
  589. ChkTime,User,Format,  Rept,Elem,i,maxLen,S,When) {
  590.     if (Warn) {
  591.     if (ChkTime+0 != 0) {
  592.         When = sprintf("%s ago, at %s",approxTime(systime()-ChkTime),
  593.         FormatTime("%I:%M %p %Z",ChkTime))
  594.     }
  595.     else
  596.         When = ChkTime
  597.     split(sprintf(\
  598. "\n"\
  599. "  W A R N I N G  \n"\
  600. "\n"\
  601. "YOU ARE EXCEEDING YOUR DISK QUOTA ON %s.\n"\
  602. "YOU MUST REDUCE YOUR DISK USAGE OF %sK\n"\
  603. "BY %sK (TO %sK) OR RISK HAVING FILES\n"\
  604. " REMOVED \n"\
  605. "(last checked %s)\n",
  606.     MountDir,DUsage,DUsage-Quota,Quota,When),Elem,"\n")
  607.     for (i = 1; i in Elem; i++)
  608.         maxLen = max(length(Elem[i]),maxLen)
  609.     for (i = 1; i in Elem; i++) {
  610.         S[1] = Elem[i]
  611.         Rept = Rept MakeRow("*","*","",
  612.         (S[1] ~ /^( |$)/) ? "*" : " ",maxLen+3,1,S) "\n"
  613.     }
  614.     return "\007\007\007\007" Rept
  615.     }
  616.     else {
  617.     if (Parseable)
  618.         Format = " %s %d %d"
  619.     if (IncUser)
  620.         Rept = sprintf("%8s ",User)
  621.     OverUsage = DUsage - Quota
  622.     return Rept sprintf(Format,MountDir,DUsage,Quota,
  623.     ((OverUsage > 0) ? OverUsage : "-"),FormatTime(tfTime,ChkTime))
  624.     }
  625. }
  626.  
  627. # Left & right are the characters for the left & right edges;
  628. # Sep is for the horizontal separators, and Fill is for the characters
  629. # in between.  Width is the cell width.  CellsAcross is the number of
  630. # cells to make.
  631. # S[1..CellsAcross] contains strings to center in the spaces in the cells,
  632. # padded on left & right with Fill.
  633. function MakeRow(Left,Right,Sep,Fill,Width,CellsAcross,S,
  634. Line,i,BlankFill,Template) {
  635.     for (i = 1; i < Width; i++)
  636.     Template = Template Fill
  637.     for (i = 1; i <= CellsAcross; i++) {
  638.     if (i in S) {
  639.         Len = length(S[i])
  640.         Blank = (Width - Len - 1) / 2
  641.         BlankFill = \
  642.         substr(Template,1,Blank) S[i] substr(Template,1,Blank + 0.6)
  643.     }
  644.     else
  645.         BlankFill = Template
  646.     if (i < CellsAcross)
  647.         Line = Line BlankFill Sep
  648.     else
  649.         Line = Line BlankFill
  650.     }
  651.     return Left Line Right
  652. }
  653.  
  654. function approxTime(time,  units) {
  655.     if (time < 60)
  656.     units = "second"
  657.     else if ((time/=60) < 60)
  658.     units = "minute"
  659.     else if ((time/=60) < 24)
  660.     units = "hour"
  661.     else if ((time/=24) < 7)
  662.     units = "day"
  663.     else if ((time/=7) < (365.25/7))
  664.     units = "week"
  665.     else {
  666.     time/=(365.25/7)
  667.     units = "year"
  668.     }
  669.     time = int(time)
  670.     if (time != 1)
  671.     units = units "s"
  672.     return time " " units
  673. }
  674.  
  675. # Return the quota and warn value for user for the filesys mounted on MountDir
  676. # The quota is returned in Values["quota"], warn value in Values["warn"].
  677. # If the user has no quota, 0 is returned; if the user does not exist, -1 is
  678. # returned; otherwise 1 is returned.
  679. # Uses global BadUsers[] to record bad usernames.
  680. function GetQuota(Values,User,MountDir,Quotas,WarnQuot,RealUsers,  Index) {
  681.     Index = MountDir SUBSEP User
  682.  
  683.     if (Index in Quotas) {
  684.     Quota = Quotas[Index]
  685.     if (Quota == -1)
  686.         return 0    # No quota
  687.     WarnLevel = WarnQuot[Index]
  688.     }
  689.     else if (User == "DEFAULT")
  690.     # If DEFAULT is not given a specific quota, it defaults to no quota.
  691.     return 0
  692.     else {
  693.     # Users who are not mentioned in the Quotas file get the default
  694.     # quota.  But, must ensure that only real users are given the
  695.     # default quota, else quota given a bogus name (probably as a
  696.     # result of a typo) will give that nonexistant user the default
  697.     # quota and not indicate an error.  So, check in /etc/passwd for
  698.     # such users, but only if we don't already know they exist, because
  699.     # the passwd routines are slow (can add several seconds to run
  700.     # time).  Users already known to exist are the invoking user and
  701.     # any user whose name appeared in any of the quota or usage files.
  702.     if (!(User in RealUsers) && !getpwnam(User,PWent)) {
  703.         if (!(User in BadUsers)) {
  704.         # Only report on a bad user once
  705.         printf "%s: No such user.\n",User > "/dev/stderr"
  706.         BadUsers[User]
  707.         }
  708.         return -1
  709.     }
  710.     if ((MountDir,"DEFAULT") in Quotas) {
  711.         Quota = Quotas[MountDir,"DEFAULT"]
  712.         WarnLevel = WarnQuot[MountDir,"DEFAULT"]
  713.     }
  714.     else
  715.         return 0    # No quota
  716.     }
  717.     Values["quota"] = Quota
  718.     Values["warn"] = WarnLevel
  719.     return 1
  720. }
  721.  
  722. # If there is a Usage file in MountDir, reads it to find usage for each
  723. # user that is an index of Users.
  724. # Puts the usage (in Kbytes) for each user in Usage[MountDir,User].
  725. # If inode counts given, puts the count for each user in Inodes[MountDir,User].
  726. # Puts the time the usage file was last updated in Usage[MountDir].
  727. # If Quota is >= 0, the usage of any user with a usage higher than Quota
  728. # is also put in Usage[]; likewise for IQuota and Inodes[].
  729. # If OverUse is true, the usage of any user with a usage higher than the
  730. # user's warning level (from WarnQuot) is also put in Usage[]; likewise for
  731. # IWarnQuot and Inodes[].
  732. # If the file doesn't exist or any other error occurs, returns nonzero.
  733. # NamesGiven is true if user names were given on the command line.  If it is
  734. # true and either OverUse is true or Quota > -1, any user exceeding the warning
  735. # level is added to Users[]; likewise for inodes.
  736. function GetUsage(MountDir,Users,WarnQuot,IWarnQuot,OverUse,Usage,Inodes,Quota,
  737. IQuota,NamesGiven,
  738. UsageFile,ret,Line,Index,WarnLevel,IWarnLevel,Cmd,Date,User,K,IRec) {
  739.     UsageFile = MountDir "/Usage"
  740.     # If a raw quot output file, the first line of the file will the the 
  741.     # fs name; if a Usage file, it will be the date.
  742.     getline < UsageFile
  743.     if (NF > 2) {    # save time of last update
  744.     if ($1 + 0 > 0)
  745.         # new format: 1st field is UNIX epoch time, which is better
  746.         # than preformatted time because it can be formatted by quota
  747.         # using the user's timezone, and various format strings.
  748.         Usage[MountDir] = $1
  749.     else
  750.         Usage[MountDir] = $0
  751.     }
  752.     while ((ret = (getline < UsageFile)) == 1) {
  753.     Line++
  754.     if (IRec = (NF == 3))    # Blocks and inodes
  755.         User = $3
  756.     else if (NF == 2)    # Block usage only
  757.         User = $2
  758.     else        # garbage
  759.         continue
  760.     if ($1 !~ "^[0-9]+") {
  761.         if (!uid)
  762.         printf "%s: Bad usage on line %d of usage file %s:\n%s\n",
  763.         Name,Line,UsageFile,$0 > "/dev/stderr"
  764.         break
  765.     }
  766.     RealUsers[User]
  767.     Index = MountDir SUBSEP User
  768.     if (OverUse) {
  769.         if (Index in WarnQuot)
  770.         WarnLevel = WarnQuot[Index]
  771.         else
  772.         WarnLevel = WarnQuot[MountDir,"DEFAULT"]
  773.         if (IRec && Index in IWarnQuot)
  774.         IWarnLevel = IWarnQuot[Index]
  775.         else
  776.         IWarnLevel = IWarnQuot[MountDir,"DEFAULT"]
  777.     }
  778.     K = $1 / 2
  779.     if (Quota > -1 && K >= Quota || OverUse && K > WarnLevel ||
  780.     User in Users) {
  781.         Usage[Index] = K
  782.         if (!NamesGiven && (OverUse || Quota > -1))
  783.         Users[User]
  784.     }
  785. # must wait until the rest of the code is ready
  786. #    if (IRec && (IQuota > -1 && $2 >= IQuota || OverUse && $2 > IWarnLevel\
  787. #    || User in Users)) {
  788. #        ;
  789. #        Inodes[Index] = $2
  790. #        if (!NamesGiven && (OverUse || IQuota > -1))
  791. #        Users[User]
  792. #    }
  793.     }
  794.     close(UsageFile)
  795.     return ret
  796. }
  797.  
  798. # Return values: quota, or -1 for no quota, or -2 for error
  799. function QuotaVal(Val,MountDir,Quotas,Line,QuotaFile) {
  800.     if (Val ~ "^[0-9]+")
  801.     return (Val)+0
  802.     else if (Val == "-")
  803.     return -1
  804.     else {
  805.     if (!((MountDir,Val) in Quotas)) {
  806.         if (!uid)
  807.         printf "%s: Bad quota on line %d of quota file %s:\n"\
  808.         "undefined value '%s'\n%s\n",Name,
  809.         Line,QuotaFile,Val,$0 > "/dev/stderr"
  810.         return -2
  811.     }
  812.     return Quotas[MountDir,Val]    # Use alias value
  813.     }
  814. }
  815.  
  816. # If there is a Quotas file in MountDir, reads it to find quota for each user.
  817. # Puts the quota for each user in Quotas[MountDir,User]
  818. # and the warning level for each user in WarnQuot[MountDir,User].
  819. # If the file doesn't exist or any other error occurs, returns nonzero.
  820. # If UseReal is true, the real quota is used for the warning threshold.
  821. function GetQuotas(MountDir,Quotas,WarnQuot,UseReal,
  822. QuotaFile,ret,Line,RealQuota,WarnLevel,User) {
  823.     QuotaFile = MountDir "/Quotas"
  824.     while ((ret = (getline < QuotaFile)) == 1) {
  825.     Line++
  826.     if (NF > 0 && $1 !~ "^#") {
  827.         if (NF != 3 && NF != 2) {
  828.         if (!uid)
  829.             printf "%s: Bad quota on line %d of quota file %s:\n"\
  830.             "Wrong number of fields.\n%s\n",Name,
  831.             Line,QuotaFile,$0 > "/dev/stderr"
  832.         }
  833.         else {    
  834.         # Process everyone since any name might be an alias used later
  835.         if ((RealQuota = \
  836.         QuotaVal($2,MountDir,Quotas,Line,QuotaFile)) == -2 ||
  837.         (WarnLevel = \
  838.         QuotaVal($NF,MountDir,WarnQuot,Line,QuotaFile)) == -2)
  839.             continue
  840.         if (WarnLevel < RealQuota) {
  841.             if (!uid)
  842.             printf "%s: Bad quota on line %d of quota file %s:\n"\
  843.             "warning level less than quota\n%s\n",Name,
  844.             Line,QuotaFile,$0 > "/dev/stderr"
  845.             WarnLevel = RealQuota
  846.         }
  847.         RealUsers[User = $1]
  848.         Quotas[MountDir,User] = RealQuota
  849.         if (UseReal)
  850.             WarnQuot[MountDir,User] = RealQuota
  851.         else
  852.             WarnQuot[MountDir,User] = WarnLevel
  853.         }
  854.     }
  855.     }
  856.     close(QuotaFile)
  857.     return ret
  858. }
  859.  
  860. # For each mounted filesystem reported by mount(NADM),
  861. # puts the mount directory in MountDirs with an index of its filesystem
  862. # device name.
  863. function GetMount(MountDirs,  Cmd,ret) {
  864.     Cmd = "exec /etc/mount"
  865.     while ((ret = (Cmd | getline)) == 1)
  866.     MountDirs[$3] = $1
  867.     close(Cmd)
  868.     return ret
  869. }
  870.  
  871. # id returns the user name of the user who owns the current process.
  872. # In the array IDs, elements are set as follows:
  873. # uid: numeric user id
  874. # gid: numeric group id
  875. # group: group name, if any
  876. # user: user name, if any
  877. function id(IDs,  Cmd,line,elem) {
  878.     Cmd = "exec id"
  879.     Cmd | getline line
  880.     split(line,elem,"[()=]")
  881.     close(Cmd)
  882.     IDs["user"] = elem[3]
  883.     IDs["gid"] = elem[5]
  884.     IDs["group"] = elem[6]
  885.     return IDs["uid"] = elem[2]
  886. }
  887.  
  888. ### Begin pwent library
  889.  
  890. # @(#) pwent.awk 1.2 96/06/27
  891. # 92/08/10 john h. dubois III (john@armory.com)
  892. # 93/12/13 fixed to not clobber $*
  893. # 96/01/05 Send error messages to /dev/stderr
  894. # 96/05/24 Let getpwnam() return a specific field if requested.
  895. #          Added PW_REAL and PW_OFFICE.
  896. # 96/06/03 Added Type field to getpwent()
  897. # 96/06/24 Allow a Field to be requested for getpwent() also.
  898. # 96/06/29 Added PW_RECORD, and getpwreal().
  899. #          Changed PWLines to be index by record number instead of name.
  900. # 96/11/17 Added getpwuid()
  901.  
  902. # Require: ReadShells()
  903.  
  904. # getpwent, getpwnam: get an entry from the passwd file.
  905. # Each of the following passwd functions returns an array which contains
  906. # a passwd file entry.  The array contains the fields of the entry.
  907. # Global variables:
  908. # The following variables are defined with the values of the indexes of the
  909. # entries: PW_NAME, PW_PASSWORD, PW_UID, PW_GID, PW_GCOS, PW_HOME, PW_SHELL
  910. # PWLines[] contains the lines of the password file, indexed by record number,
  911. # starting with 1.
  912. # _pwNames[] is a mapping of name to passwd record number.
  913. # getpwentNum is the number of the next entry to be returned by getpwent().
  914.  
  915. # Left FS global because making it local does not work in gawk.
  916. function ReadPasswd(  User,Line,i,Ind,ret,OFS) {
  917.     if (PW_Name)
  918.     return 1
  919.     PW_NAME = 1
  920.     PW_PASSWORD = 2
  921.     PW_UID = 3
  922.     PW_GID = 4
  923.     PW_GCOS = 5
  924.     PW_HOME = 6
  925.     PW_SHELL = 7
  926.     PW_REAL = -1    # for PWGetFields()
  927.     PW_OFFICE = -2
  928.     PW_RECORD = -3
  929.  
  930.     Ind = getpwentNum = 1
  931.     OFS = FS
  932.     FS = ":"
  933.     while ((ret = (getline Line < "/etc/passwd")) == 1) {
  934.     User = Line
  935.     sub(":.*","",User)
  936.     _pwNames[User] = Ind
  937.     PWLines[Ind++] = Line
  938.     }
  939.     FS = OFS
  940.     close("/etc/passwd")
  941.     if (ret) {
  942.     printf "ReadPasswd(): Could not open /etc/passwd.\n" > "/dev/stderr"
  943.     return 0
  944.     }
  945.     return 1
  946. }
  947.  
  948. # setpwent resets the passwd file entry pointer used by getpwent
  949. # to the first entry.
  950. function setpwent() {
  951.     getpwentNum = 1
  952. }
  953.  
  954. # getpwent sets PWEnt to the next entry in the passwd file.
  955. # If Type is set to -1, the entry for the next "real" user is returned (others
  956. # are skipped over), where a real user is a user whose login shell is listed in
  957. # /etc/shells.  This requires the ReadShells() function.  Other values for
  958. # Type are not yet defined and are ignored.
  959. # If the last entry has already been returned, 0 is return if Field is null,
  960. # ":" if not.
  961. # If the entry for the next real user has been requested and /etc/shells
  962. # cannot be read, -1 is returned if Field is null, "\n" if not.
  963. # See PWGetFields() for other return values and the meaning of the Field
  964. # parameter.
  965. function getpwent(PWEnt,Type,Field,  entNum) {
  966.     if (!PW_NAME)
  967.     ReadPasswd()
  968.     if (!(getpwentNum in PWLines))
  969.     return Field ? ":" : 0
  970.     if (Type == -1) {
  971.     if (!_DidReadShells && ReadShells(LoginShells) == -1)
  972.         return Field ? "\n" : -1
  973.     split(PWLines[getpwentNum++],PWEnt,":")
  974.     while (!(PWEnt[PW_SHELL] in LoginShells)) {
  975.         if (!(getpwentNum in PWLines))
  976.         return Field ? ":" : 0
  977.         split(PWLines[getpwentNum++],PWEnt,":")
  978.     }
  979.     return PWGetFields("",PWEnt,Field,getpwentNum - 1)
  980.     }
  981.     else {
  982.     entNum = getpwentNum
  983.     return PWGetFields(PWLines[getpwentNum++],PWEnt,Field,entNum)
  984.     }
  985. }
  986.  
  987. function MakeInd(  Elem,Ind,Line,uid,home) {
  988.     for (Ind = 1; Ind in PWLines; Ind++) {
  989.     Line = PWLines[Ind]
  990.     split(Line,Elem,":")
  991.     uid = Elem[PW_UID]
  992.     if (!(uid in uidInd))
  993.         uidInd[uid] = Ind
  994.     home = Elem[PW_HOME]
  995.     if (!(home in HomeInd))
  996.         HomeInd[home] = Ind
  997.     }
  998.     IndDone = 1
  999. }
  1000.  
  1001. # PWGetFields() splits PWLine into PWEnt[], and optionally returns a field
  1002. # from it.  If PWLine is null, PWEnt[] is assumed to have already been filled
  1003. # in with a password entry.
  1004. # If Field is not passed or is null, the return value is 1.
  1005. # If Field is non-null, it should a PW_ value.  In this case, the value of the
  1006. # requested field is returned.
  1007. # entNum is the value that PWEnt[PW_RECORD] should be set to.  It should be
  1008. # the index in PWLines[] of the record being processed.
  1009. # In addition to the PW_ values used by the rest of the functions in this
  1010. # library, this function can be passed PW_REAL and PW_OFFICE.
  1011. # PW_REAL will get the part of the GCOS field before the first comma.
  1012. # PW_OFFICE will get the part of the GCOS field after the first comma.
  1013. # If either of these is requested, both values will also be assigned to their
  1014. # indices in PWEnt[], unless there is no comma in the GCOS field, in which case
  1015. # PW_OFFICE will not be set.
  1016. # NOTE: since the global field names are set in ReadShells(), it must be
  1017. # executed before any of the field name can be passed.
  1018. function PWGetFields(PWLine,PWEnt,Field,entNum,  gcos,ind) {
  1019.     if (PWLine != "")
  1020.     split(PWLine,PWEnt,":")
  1021.     PWEnt[PW_RECORD] = entNum
  1022.     if (!Field)
  1023.     return 1
  1024.     if (Field < 0) {
  1025.     if (ind = index(gcos = PWEnt[PW_GCOS],",")) {
  1026.         PWEnt[PW_OFFICE] = substr(gcos,ind+1)
  1027.         PWEnt[PW_REAL] = substr(gcos,1,ind-1)
  1028.     }
  1029.     else
  1030.         PWEnt[PW_REAL] = gcos
  1031.     }
  1032.     return PWEnt[Field]
  1033. }
  1034.  
  1035. # getpwnam sets PWEnt to the passwd entry for login name Name.
  1036. # If Name does not exist in the password file, the return value is ":"
  1037. # if Field was passed, 0 if not.
  1038. # For other return values and parameter explanation, see PWGetFields()
  1039. function getpwnam(Name,PWEnt,Field) {
  1040.     if (!PW_NAME)
  1041.     ReadPasswd()
  1042.     if (Name in _pwNames)
  1043.     return PWGetFields(PWLines[_pwNames[Name]],PWEnt,Field,_pwNames[Name])
  1044.     else
  1045.     return Field ? ":" : 0
  1046. }
  1047.  
  1048. # getpwhome sets PWEnt to the passwd entry whose home dir is Home.
  1049. # See getpwnam() for return values and the meaning of the Field param.
  1050. function getpwhome(Home,PWEnt,Field) {
  1051.     if (!PW_NAME)
  1052.     ReadPasswd()
  1053.     if (!IndDone)
  1054.     MakeInd()
  1055.     if (Home in HomeInd)
  1056.     return PWGetFields(PWLines[HomeInd[Home]],PWEnt,Field,HomeInd[Home])
  1057.     else
  1058.     return Field ? ":" : 0
  1059. }
  1060.  
  1061. # getpwuid sets PWEnt to the passwd entry whose uid is UID.
  1062. # See getpwnam() for return values and the meaning of the Field param.
  1063. function getpwuid(UID,PWEnt,Field) {
  1064.     if (!PW_NAME)
  1065.     ReadPasswd()
  1066.     if (!IndDone)
  1067.     MakeInd()
  1068.     if ((UID + 0) in uidInd)
  1069.     return PWGetFields(PWLines[uidInd[UID]],PWEnt,Field,uidInd[UID])
  1070.     else
  1071.     return Field ? ":" : 0
  1072. }
  1073.  
  1074. # Make an index by real name.  For each passwd file entry, the real-name
  1075. # is lowercased and split into components on non-alphanums.   The passwd entry
  1076. # index that the name came from is added to the value of each such component
  1077. # in the global _RealInd[].  The indexes stored this way are separated by
  1078. # commas.  If the real-name contains no alphanums, its index is stored under
  1079. # the null index.
  1080. function _makeRealInd(  PWEnt,ret,Elem,nelem,i,Component) {
  1081.     setpwent()
  1082.     while ((ret = getpwent(PWEnt,"",PW_REAL)) != ":") {
  1083.     nelem = split(tolower(ret),Elem,/[^a-z0-9]+/)
  1084.     for (i = 1; i <= nelem; i++) {
  1085.         Component = Elem[i]
  1086.         if (Component == "" && nelem > 1)
  1087.         continue
  1088.         if (Component in _RealInd)
  1089.         _RealInd[Component] = _RealInd[Component] "," PWEnt[PW_RECORD]
  1090.         else
  1091.         _RealInd[Component] = PWEnt[PW_RECORD]
  1092.     }
  1093.     }
  1094.     _realIndDone = 1
  1095. }
  1096.  
  1097. # Make Name into a pattern that will match a name that contains all of the
  1098. # same name components (sequences of alphanums) in the same order.  If Name
  1099. # contains no name components, a null string is returned.
  1100. function MakeNamePat(Name,  Elem,nelem,i,Pat,e) {
  1101.     nelem = split(Name,Elem,/[^a-zA-Z0-9]+/)
  1102.     for (i = 1; i <= nelem; i++) {
  1103.     if ((e = Elem[i]) == "")
  1104.         continue
  1105.     if (Pat == "")
  1106.         Pat = "(^|[^a-zA-Z0-9])" e
  1107.     else
  1108.         Pat = Pat "[^a-zA-Z0-9](.*[^a-zA-Z0-9])?" e
  1109.     }
  1110.     if (Pat == "")    # If Name contained no alphanums...
  1111.     return ""
  1112.     Pat = Pat "([^a-zA-Z0-9]|$)"
  1113.     return Pat
  1114. }
  1115.  
  1116. # getpwgreal sets PWEnt to the first passwd entry whose PW_REAL (see
  1117. # PWGetFields()) field matches Real.  Matching occurs if the alphanumeric
  1118. # components of Real occur in the same order in the entry.  Non-alphanums are
  1119. # ignored.  All of the components in Real must occur in the entry, but not all
  1120. # of the components in the entry must occur in Real.
  1121. # If the given name does not exist in the password file,
  1122. # the return value is ":" if Field was passed, 0 if not.
  1123. # If Next is true, getpwreal() sets PWEnt to the next passwd entry whose
  1124. # PW_REAL field matches the last previous Real parameter passed.
  1125. # In this case,  if the last entry has already been returned,
  1126. # the return value is ":" if Field was passed, 0 if not.
  1127. # Different IgnoreCase and Full parameters may be given when doing a Next
  1128. # search.  Both must always be passed; they do not default to the original
  1129. # values when doing a Next search.  The only parameter ignored when doing a
  1130. # Next search is Real.
  1131. # If IgnoreCase is true, case is ignored when searching.
  1132. # If Full is true, a match of the full name is required (including any
  1133. # punctuation).
  1134. # For successful return values and Field parameter explanation,
  1135. # see PWGetFields()
  1136. # Globals: For the Next search, between invokations these varies store values:
  1137. # _getpwrealInd[]: The set of pw indices that matched the query.
  1138. # _getpwrealIndInd: The next index in _getpwrealInd[] to look at.
  1139. # _getpwrealReal: The Real value passed with the original query.
  1140. # _getpwrealPat: Real converted to a component order search pattern.
  1141. function getpwreal(Real,PWEnt,Field,IgnoreCase,Full,Next,  ind,name,Pat) {
  1142.     if (!Next) {
  1143.     if (!PW_NAME)
  1144.         ReadPasswd()
  1145.     if (!_realIndDone)
  1146.         _makeRealInd()
  1147.     _getpwrealReal = Real
  1148.     _getpwrealPat = MakeNamePat(Real)
  1149.     # Get first component from Real
  1150.     Real = tolower(Real)
  1151.     gsub("^[^a-z0-9]+","",Real)
  1152.     gsub("[^a-z0-9].*","",Real)
  1153.     if (!(Real in _RealInd))
  1154.         return Field ? ":" : 0
  1155.     split(_RealInd[Real],_getpwrealInd,",")
  1156.     _getpwrealIndInd = 1
  1157.     }
  1158.     if (Full)
  1159.     Pat = _getpwrealReal
  1160.     else
  1161.     Pat = _getpwrealPat
  1162.     if (IgnoreCase)
  1163.     Pat = tolower(Pat)
  1164.     while (_getpwrealIndInd in _getpwrealInd) {
  1165.     ind = _getpwrealInd[_getpwrealIndInd++]
  1166.     name = PWGetFields(PWLines[ind],PWEnt,PW_REAL,ind)
  1167.     if (IgnoreCase)
  1168.         name = tolower(name)
  1169.     if (Full ? (name == Pat) : (name ~ Pat))
  1170.         return PWGetFields("",PWEnt,Field,ind)
  1171.     }
  1172.     return Field ? ":" : 0
  1173. }
  1174.  
  1175. ### End pwent library
  1176.  
  1177. # Returns 1 if Set is empty, 0 if not.
  1178. function IsEmpty(Set,  i) {
  1179.     for (i in Set)
  1180.     return 0
  1181.     return 1
  1182. }
  1183.  
  1184. ### Begin min,max,In routines
  1185.  
  1186. function min(a,b) {
  1187.     if (a < b)
  1188.     return a
  1189.     else
  1190.     return b
  1191. }
  1192.  
  1193. function max(a,b) {
  1194.     if (a > b)
  1195.     return a
  1196.     else
  1197.     return b
  1198. }
  1199.  
  1200. function In(Val,Min,Max) {
  1201.     return (Min <= Val && Val <= Max)
  1202. }
  1203.  
  1204. # Return (in Ind) the indices of the elements with the smallest value in A.
  1205. # The smallest value is returned as the function value.
  1206. # If there are no elements in A, null is returned.
  1207. function arrMin(A,Ind,  i,min) {
  1208.     for (i in A)
  1209.     if (min == "" || A[i] < min) {
  1210.         DeleteAll(Ind)
  1211.         min = A[i]
  1212.         Ind[i]
  1213.     }
  1214.     else if (A[i] == min)
  1215.         Ind[i]
  1216.     return min
  1217. }
  1218.  
  1219. ### End min,max,In routines
  1220. ### Begin qsort routines
  1221.  
  1222. # Arr[] is an array of values with arbitrary indices.
  1223. # k[] is returned with numeric indices 1..n.
  1224. # The values in k[] are the indices of Arr[],
  1225. # ordered so that if Arr[] is stepped through
  1226. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1227. # through in order of the values of its elements.
  1228. # The return value is the number of elements in the arrays (n).
  1229. function qsortArbIndByValue(Arr,k,  ArrInd,ElNum) {
  1230.     ElNum = 0
  1231.     for (ArrInd in Arr)
  1232.     k[++ElNum] = ArrInd
  1233.     qsortSegment(Arr,k,1,ElNum)
  1234.     return ElNum
  1235. }
  1236.  
  1237. # Sort a segment of an array.
  1238. # Arr[] contains data with arbitrary indices.
  1239. # k[] has indices 1..nelem, with the indices of arr[] as values.
  1240. # This function sorts the elements of arr that are pointed to by
  1241. # k[start..end], swapping the values of elements of k[] so that
  1242. # when this function returns arr[k[start..end]] will be in order.
  1243. function qsortSegment(Arr,k,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1244.     # handle two-element case explicitly for a tiny speedup
  1245.     if ((end - start) == 1) {
  1246.     if (Arr[tmps = k[start]] > Arr[tmpe = k[end]]) {
  1247.         k[start] = tmpe
  1248.         k[end] = tmps
  1249.     }
  1250.     return
  1251.     }
  1252.     # Make sure comparisons act on these as numbers
  1253.     left = start+0
  1254.     right = end+0
  1255.     sepval = Arr[k[int((left + right) / 2)]]
  1256.     # Make every element <= sepval be to the left of every element > sepval
  1257.     while (left < right) {
  1258.     while (Arr[k[left]] < sepval)
  1259.         left++
  1260.     while (Arr[k[right]] > sepval)
  1261.         right--
  1262.     if (left < right) {
  1263.         tmp = k[left]
  1264.         k[left++] = k[right]
  1265.         k[right--] = tmp
  1266.     }
  1267.     }
  1268.     if (left == right)
  1269.     if (Arr[k[left]] < sepval)
  1270.         left++
  1271.     else
  1272.         right--
  1273.     if (start < right)
  1274.     qsortSegment(Arr,k,start,right)
  1275.     if (left < end)
  1276.     qsortSegment(Arr,k,left,end)
  1277. }
  1278.  
  1279. # Arr[] is an array of values with arbitrary indices.
  1280. # k[] is returned with numeric indices 1..n.
  1281. # The values in k are the indices of Arr[],
  1282. # ordered so that if Arr[] is stepped through
  1283. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1284. # through in order of the values of its indices.
  1285. # The return value is the number of elements in the arrays (n).
  1286. # If the indexes are numeric, Numeric should be true, so that they can be
  1287. # compared as such rather than as strings.  Numeric indexes do not have to be
  1288. # contiguous.
  1289. function qsortByArbIndex(Arr,k,Numeric,  ArrInd,ElNum) {
  1290.     ElNum = 0
  1291.     if (Numeric)
  1292.     # Indexes do not preserve numeric type, so must be forced
  1293.     for (ArrInd in Arr)
  1294.         k[++ElNum] = ArrInd+0
  1295.     else
  1296.     for (ArrInd in Arr)
  1297.         k[++ElNum] = ArrInd
  1298.     qsortNumIndByValue(k,1,ElNum)
  1299.     return ElNum
  1300. }
  1301.  
  1302. # Arr is an array of elements with contiguous numeric indexes to be sorted
  1303. # by value.
  1304. # start and end are the starting and ending indexes of the range to be sorted.
  1305. function qsortNumIndByValue(Arr,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1306.     # handle two-element case explicitly for a tiny speedup
  1307.     if ((start - end) == 1) {
  1308.     if ((tmps = Arr[start]) > (tmpe = Arr[end])) {
  1309.         Arr[start] = tmpe
  1310.         Arr[end] = tmps
  1311.     }
  1312.     return
  1313.     }
  1314.     left = start+0
  1315.     right = end+0
  1316.     sepval = Arr[int((left + right) / 2)]
  1317.     while (left < right) {
  1318.     while (Arr[left] < sepval)
  1319.         left++
  1320.     while (Arr[right] > sepval)
  1321.         right--
  1322.     if (left <= right) {
  1323.         tmp = Arr[left]
  1324.         Arr[left++] = Arr[right]
  1325.         Arr[right--] = tmp
  1326.     }
  1327.     }
  1328.     if (start < right)
  1329.     qsortNumIndByValue(Arr,start,right)
  1330.     if (left < end)
  1331.     qsortNumIndByValue(Arr,left,end)
  1332. }
  1333.  
  1334. ### End qsort routines
  1335. ### Start of ProcArgs library
  1336. # @(#) ProcArgs 1.12 97/02/22
  1337. # 92/02/29 john h. dubois iii (john@armory.com)
  1338. # 93/07/18 Added "#" arg type
  1339. # 93/09/26 Do not count -h against MinArgs
  1340. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  1341. #          Removed meaning of "+" or "-" by itself.
  1342. # 94/03/08 Added & option and *()< option types.
  1343. # 94/04/02 Added NoRCopt to Opts()
  1344. # 94/06/11 Mark numeric variables as such.
  1345. # 94/07/08 Opts(): Do not require any args if h option is given.
  1346. # 95/01/22 Record options given more than once.  Record option num in argv.
  1347. # 95/06/08 Added ExclusiveOptions().
  1348. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  1349. #          Expand $VARNAME at the start of its filenames.
  1350. #          Let varname=0 and -option- turn off an option.
  1351. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  1352. #          of the vars should be searched for in the environment.
  1353. #          Check for duplicate rcfiles.
  1354. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  1355. #          now return various negatives values on error, not just -1, and
  1356. #          Opts() may set Err to various positive values, not just 1.
  1357. #          Added AllowUnrecOpt.
  1358. # 96/05/23 Check type given for & option
  1359. # 96/06/15 Re-port to awk
  1360. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  1361. #          used by other functions.
  1362. # 96/10/15 Added OptChars
  1363. # 96/11/01 Added exOpts arg to Opts()
  1364. # 96/11/16 Added ; type
  1365. # 96/12/08 Added Opt2Set() & Opt2Sets()
  1366. # 96/12/27 Added CmdLineOpt()
  1367. # 97/02/22 Remove packed elements.
  1368. # 97/02/28 Make sequence # for rcfiles & environ be "f" and "e".
  1369. #          Replaced CmdLineOpt() with OptsGiven().
  1370.  
  1371. # optlist is a string which contains all of the possible command line options.
  1372. # A character followed by certain characters indicates that the option takes
  1373. # an argument, with type as follows:
  1374. # :    String argument
  1375. # ;    Non-empty string argument
  1376. # *    Floating point argument
  1377. # (    Non-negative floating point argument
  1378. # )    Positive floating point argument
  1379. # #    Integer argument
  1380. # <    Non-negative integer argument
  1381. # >    Positive integer argument
  1382. # The only difference the type of argument makes is in the runtime argument
  1383. # error checking that is done.
  1384.  
  1385. # The & option is a special case used to get numeric options without the
  1386. # user having to give an option character.  It is shorthand for [-+.0-9].
  1387. # If & is included in optlist and an option string that begins with one of
  1388. # these characters is seen, the value given to "&" will include the first
  1389. # char of the option.  & must be followed by a type character other than ":"
  1390. # or ";".
  1391. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  1392.  
  1393. # Strings in argv[] which begin with "-" or "+" are taken to be
  1394. # strings of options, except that a string which consists solely of "-"
  1395. # or "+" is taken to be a non-option string; like other non-option strings,
  1396. # it stops the scanning of argv and is left in argv[].
  1397. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  1398. # If an option takes an argument, the argument may either immediately
  1399. # follow it or be given separately.
  1400. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  1401. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  1402. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  1403. # this feature had a flaw that caused problems in some cases.  See the OptChars
  1404. # parameter to explicitly set the option-specifier characters.
  1405.  
  1406. # If an option that does not take an argument is given,
  1407. # an index with its name is created in Options and its value is set to the
  1408. # number of times it occurs in argv[].
  1409.  
  1410. # If an option that does take an argument is given, an index with its name is
  1411. # created in Options and its value is set to the value of the argument given
  1412. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  1413. # If an option that takes an argument is given more than once,
  1414. # Options[option-name,"count"] is incremented, and the value is assigned to
  1415. # the index (option-name,instance) where instance is 2 for the second occurance
  1416. # of the option, etc.
  1417. # In other words, the first time an option with a value is encountered, the
  1418. # value is assigned to an index consisting only of its name; for any further
  1419. # occurances of the option, the value index has an extra (count) dimension.
  1420.  
  1421. # The sequence number for each option found in argv[] is stored in
  1422. # Options[option-name,"num",instance], where instance is 1 for the first
  1423. # occurance of the option, etc.  The sequence number starts at 1 and is
  1424. # incremented for each option, both those that have a value and those that
  1425. # do not.  Options set from a config file get a sequence number of "f", and
  1426. # options set in the environment get a sequence number of "e".
  1427.  
  1428. # Options and their arguments are deleted from argv.
  1429. # Note that this means that there may be gaps left in the indices of argv[].
  1430. # If compress is nonzero, argv[] is packed by moving its elements so that
  1431. # they have contiguous integer indices starting with 0.
  1432. # Option processing will stop with the first unrecognized option, just as
  1433. # though -- was given except that unlike -- the unrecognized option will not be
  1434. # removed from ARGV[].  Normally, an error value is returned in this case.
  1435. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  1436. # be found, so the number of remaining arguments is returned instead.
  1437. # If OptChars is not a null string, it is the set of characters that indicate
  1438. # that an argument is an option string if the string begins with one of the
  1439. # characters.  A string consisting solely of two of the same option-indicator
  1440. # characters stops the scanning of argv[].  The default is "-+".
  1441. # argv[0] is not examined.
  1442. # The number of arguments left in argc is returned.
  1443. # If an error occurs, the global string OptErr is set to an error message
  1444. # and a negative value is returned.
  1445. # Current error values:
  1446. # -1: option that required an argument did not get it.
  1447. # -2: argument of incorrect type supplied for an option.
  1448. # -3: unrecognized (invalid) option.
  1449. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  1450. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  1451. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  1452. {
  1453. # ArgNum is the index of the argument being processed.
  1454. # ArgsLeft is the number of arguments left in argv.
  1455. # Arg is the argument being processed.
  1456. # ArgLen is the length of the argument being processed.
  1457. # ArgInd is the position of the character in Arg being processed.
  1458. # Option is the character in Arg being processed.
  1459. # Pos is the position in OptList of the option being processed.
  1460. # NumOpt is true if a numeric option may be given.
  1461.     ArgsLeft = argc
  1462.     NumOpt = index(OptList,"&")
  1463.     OptionNum = 0
  1464.     if (OptChars == "")
  1465.     OptChars = "-+"
  1466.     while (OptChars != "") {
  1467.     c = substr(OptChars,1,1)
  1468.     OptChars = substr(OptChars,2)
  1469.     OptCharSet[c]
  1470.     OptTerm[c c]
  1471.     }
  1472.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  1473.     Arg = argv[ArgNum]
  1474.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  1475.         break    # Not an option; quit
  1476.     if (Arg in OptTerm) {
  1477.         delete argv[ArgNum]
  1478.         ArgsLeft--
  1479.         break
  1480.     }
  1481.     ArgLen = length(Arg)
  1482.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  1483.         Option = substr(Arg,ArgInd,1)
  1484.         if (NumOpt && Option ~ /[-+.0-9]/) {
  1485.         # If this option is a numeric option, make its flag be & and
  1486.         # its option string flag position be the position of & in
  1487.         # the option string.
  1488.         Option = "&"
  1489.         Pos = NumOpt
  1490.         # Prefix Arg with a char so that ArgInd will point to the
  1491.         # first char of the numeric option.
  1492.         Arg = "&" Arg
  1493.         ArgLen++
  1494.         }
  1495.         # Find position of flag in option string, to get its type (if any).
  1496.         # Disallow & as literal flag.
  1497.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  1498.         if (AllowUnrecOpt) {
  1499.             Escape = 1
  1500.             break
  1501.         }
  1502.         else {
  1503.             OptErr = "Invalid option: " specGiven Option
  1504.             return -3
  1505.         }
  1506.         }
  1507.  
  1508.         # Find what the value of the option will be if it takes one.
  1509.         # NeedNextOpt is true if the option specifier is the last char of
  1510.         # this arg, which means that if the option requires a value it is
  1511.         # the next arg.
  1512.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  1513.         if (GotValue = ArgNum + 1 < argc)
  1514.             Value = argv[ArgNum+1]
  1515.         }
  1516.         else {    # Value is included with option
  1517.         Value = substr(Arg,ArgInd + 1)
  1518.         GotValue = 1
  1519.         }
  1520.  
  1521.         if (HadValue = AssignVal(Option,Value,Options,
  1522.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  1523.         specGiven)) {
  1524.         if (HadValue < 0)    # error occured
  1525.             return HadValue
  1526.         if (HadValue == 2)
  1527.             ArgInd++    # Account for the single-char value we used.
  1528.         else {
  1529.             if (NeedNextOpt) {    # option took next arg as value
  1530.             delete argv[++ArgNum]
  1531.             ArgsLeft--
  1532.             }
  1533.             break    # This option has been used up
  1534.         }
  1535.         }
  1536.     }
  1537.     if (Escape)
  1538.         break
  1539.     # Do not delete arg until after processing of it, so that if it is not
  1540.     # recognized it can be left in ARGV[].
  1541.     delete argv[ArgNum]
  1542.     ArgsLeft--
  1543.     }
  1544.     if (compress != 0) {
  1545.     dest = 1
  1546.     src = argc - ArgsLeft + 1
  1547.     if (src != dest) {
  1548.         for (count = ArgsLeft - 1; count; count--) {
  1549.         ARGV[dest] = ARGV[src]
  1550.         dest++
  1551.         src++
  1552.         }
  1553.         for (; dest < src; dest++)
  1554.         delete ARGV[dest]
  1555.     }
  1556.     }
  1557.     return ArgsLeft
  1558. }
  1559.  
  1560. # Assignment to values in Options[] occurs only in this function.
  1561. # Option: Option specifier character.
  1562. # Value: Value to be assigned to option, if it takes a value.
  1563. # Options[]: Options array to return values in.
  1564. # ArgType: Argument type specifier character.
  1565. # GotValue: Whether any value is available to be assigned to this option.
  1566. # Name: Name of option being processed.
  1567. # OptionNum: Number of this option (starting with 1) if set in argv[],
  1568. #     or 0 if it was given in a config file or in the environment.
  1569. # SingleOpt: true if the value (if any) that is available for this option was
  1570. #     given as part of the same command line arg as the option.  Used only for
  1571. #     options from the command line.
  1572. # specGiven is the option specifier character use, if any (e.g. - or +),
  1573. # for use in error messages.
  1574. # Global variables: OptErr
  1575. # Return value: negative value on error, 0 if option did not require an
  1576. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  1577. # the arg.
  1578. # Current error values:
  1579. # -1: Option that required an argument did not get it.
  1580. # -2: Value of incorrect type supplied for option.
  1581. # -3: Bad type given for option &
  1582. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  1583. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  1584.     # If option takes a value...    [
  1585.     NumTypes = "*()#<>]"
  1586.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  1587.     OptErr = "Bad type given for & option"
  1588.     return -3
  1589.     }
  1590.  
  1591.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  1592.     if (!GotValue) {
  1593.         if (Name != "")
  1594.         OptErr = "Variable requires a value -- " Name
  1595.         else
  1596.         OptErr = "option requires an argument -- " Option
  1597.         return -1
  1598.     }
  1599.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  1600.         OptErr = Err
  1601.         return -2
  1602.     }
  1603.     # Mark this as a numeric variable; will be propogated to Options[] val.
  1604.     if (ArgType != ":" && ArgType != ";")
  1605.         Value += 0
  1606.     if ((Instance = ++Options[Option,"count"]) > 1)
  1607.         Options[Option,Instance] = Value
  1608.     else
  1609.         Options[Option] = Value
  1610.     }
  1611.     # If this is an environ or rcfile assignment & it was given a value...
  1612.     else if (!OptionNum && Value != "") {
  1613.     UsedValue = 1
  1614.     # If the value is "0" or "-" and this is the first instance of it,
  1615.     # do not set Options[Option]; this allows an assignment in an rcfile to
  1616.     # turn off an option (for the simple "Option in Options" test) in such
  1617.     # a way that it cannot be turned on in a later file.
  1618.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  1619.         Instance = 1
  1620.     else
  1621.         Instance = ++Options[Option]
  1622.     # Save the value even though this is a flag
  1623.     Options[Option,Instance] = Value
  1624.     }
  1625.     # If this is a command line flag and has a - following it in the same arg,
  1626.     # it is being turned off.
  1627.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  1628.     UsedValue = 2
  1629.     if (Option in Options)
  1630.         Instance = ++Options[Option]
  1631.     else
  1632.         Instance = 1
  1633.     Options[Option,Instance]
  1634.     }
  1635.     # If this is a flag assignment without a value, increment the count for the
  1636.     # flag unless it was turned off.  The indicator for a flag being turned off
  1637.     # is that the flag index has not been set in Options[] but it has an
  1638.     # instance count.
  1639.     else if (Option in Options || !((Option,1) in Options))
  1640.     # Increment number of times this flag seen; will inc null value to 1
  1641.     Instance = ++Options[Option]
  1642.     Options[Option,"num",Instance] = OptionNum
  1643.     return UsedValue
  1644. }
  1645.  
  1646. # Option is the option letter
  1647. # Value is the value being assigned
  1648. # Name is the var name of the option, if any
  1649. # ArgType is one of:
  1650. # :    String argument
  1651. # ;    Non-null string argument
  1652. # *    Floating point argument
  1653. # (    Non-negative floating point argument
  1654. # )    Positive floating point argument
  1655. # #    Integer argument
  1656. # <    Non-negative integer argument
  1657. # >    Positive integer argument
  1658. # specGiven is the option specifier character use, if any (e.g. - or +),
  1659. # for use in error messages.
  1660. # Returns null on success, err string on error
  1661. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  1662.     if (ArgType == ":")
  1663.     return ""
  1664.     if (ArgType == ";") {
  1665.     if (Value == "")
  1666.         Err = "must be a non-empty string"
  1667.     }
  1668.     # A number begins with optional + or -, and is followed by a string of
  1669.     # digits or a decimal with digits before it, after it, or both
  1670.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  1671.     Err = "must be a number"
  1672.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  1673.     Err = "may not include a fraction"
  1674.     else if (ArgType ~ "[()<>]" && Value < 0)
  1675.     Err = "may not be negative"
  1676.     # (
  1677.     else if (ArgType ~ "[)>]" && Value == 0)
  1678.     Err = "must be a positive number"
  1679.     if (Err != "") {
  1680.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  1681.     if (Name != "")
  1682.         return ErrStr "variable " substr(Name,1,1) " " Err
  1683.     else {
  1684.         if (Option == "&")
  1685.         Option = Value
  1686.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  1687.     }
  1688.     }
  1689.     else
  1690.     return ""
  1691. }
  1692.  
  1693. # Note: only the above functions are needed by ProcArgs.
  1694. # The rest of these functions call ProcArgs() and also do other
  1695. # option-processing stuff.
  1696.  
  1697. # Opts: Process command line arguments.
  1698. # Opts processes command line arguments using ProcArgs()
  1699. # and checks for errors.  If an error occurs, a message is printed
  1700. # and the program is exited.
  1701. #
  1702. # Input variables:
  1703. # Name is the name of the program, for error messages.
  1704. # Usage is a usage message, for error messages.
  1705. # OptList the option description string, as used by ProcArgs().
  1706. # MinArgs is the minimum number of non-option arguments that this
  1707. # program should have, non including ARGV[0] and +h.
  1708. # If the program does not require any non-option arguments,
  1709. # MinArgs should be omitted or given as 0.
  1710. # rcFiles, if given, is a colon-seprated list of filenames to read for
  1711. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  1712. # by the value of the environment variable HOME.  If a filename begins with
  1713. # $, the part from the character after the $ up until (but not including)
  1714. # the first character not in [a-zA-Z0-9_] will be searched for in the
  1715. # environment; if found its value will be substituted, if not the filename will
  1716. # be discarded.
  1717. # rcfiles are read in the order given.
  1718. # Values given in them will not override values given on the command line,
  1719. # and values given in later files will not override those set in earlier
  1720. # files, because AssignVal() will store each with a different instance index.
  1721. # The first instance of each variable, either on the command line or in an
  1722. # rcfile, will be stored with no instance index, and this is the value
  1723. # normally used by programs that call this function.
  1724. # VarNames is a comma-separated list of variable names to map to options,
  1725. # in the same order as the options are given in OptList.
  1726. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  1727. # searched for in the environment.  If set to -1, all values will be searched
  1728. # for in the environment.  Values given in the environment will override
  1729. # those given in the rcfiles but not those given on the command line.
  1730. # NoRCopt, if given, is an additional letter option that if given on the
  1731. # command line prevents the rcfiles from being read.
  1732. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  1733. # ExclusiveOptions() for a description of exOpts.
  1734. # Special options:
  1735. # If x is made an option and is given, some debugging info is output.
  1736. # h is assumed to be the help option.
  1737.  
  1738. # Global variables:
  1739. # The command line arguments are taken from ARGV[].
  1740. # The arguments that are option specifiers and values are removed from
  1741. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  1742. # The number of elements in ARGV[] should be in ARGC.
  1743. # After processing, ARGC is set to the number of elements left in ARGV[].
  1744. # The option values are put in Options[].
  1745. # On error, Err is set to a positive integer value so it can be checked for in
  1746. # an END block.
  1747. # Return value: The number of elements left in ARGV is returned.
  1748. # Must keep OptErr global since it may be set by InitOpts().
  1749. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  1750. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  1751.     if (MinArgs == "")
  1752.     MinArgs = 0
  1753.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  1754.     optChars)
  1755.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  1756.     if (ArgsLeft >= 0) {
  1757.         OptErr = "Not enough arguments"
  1758.         Err = 4
  1759.     }
  1760.     else
  1761.         Err = -ArgsLeft
  1762.     printf "%s: %s.\nUse -h for help.\n%s\n",
  1763.     Name,OptErr,Usage > "/dev/stderr"
  1764.     exit 1
  1765.     }
  1766.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  1767.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  1768.     {
  1769.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  1770.     Err = -e
  1771.     exit 1
  1772.     }
  1773.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  1774.     {
  1775.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  1776.     Err = 1
  1777.     exit 1
  1778.     }
  1779.     return ArgsLeft
  1780. }
  1781.  
  1782. # ReadConfFile(): Read a file containing var/value assignments, in the form
  1783. # <variable-name><assignment-char><value>.
  1784. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  1785. # line and whitespace between the variable name and the assignment character) 
  1786. # is stripped.  Lines that do not contain an assignment operator or which
  1787. # contain a null variable name are ignored, other than possibly being noted in
  1788. # the return value.  If more than one assignment is made to a variable, the
  1789. # first assignment is used.
  1790. # Input variables:
  1791. # File is the file to read.
  1792. # Comment is the line-comment character.  If it is found as the first non-
  1793. #     whitespace character on a line, the line is ignored.
  1794. # Assign is the assignment string.  The first instance of Assign on a line
  1795. #     separates the variable name from its value.
  1796. # If StripWhite is true, whitespace around the value (whitespace between the
  1797. #     assignment char and trailing whitespace on the line) is stripped.
  1798. # VarPat is a pattern that variable names must match.  
  1799. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  1800. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  1801. #     a line; no assignment operator is needed.  These variables are set in
  1802. #     the output array with a null value.  Lines containing nothing but
  1803. #     whitespace are still ignored.
  1804. # Output variables:
  1805. # Values[] contains the assignments, with the indexes being the variable names
  1806. #     and the values being the assigned values.
  1807. # Lines[] contains the line number that each variable occured on.  A flag set
  1808. #     is record by giving it an index in Lines[] but not in Values[].
  1809. # Return value:
  1810. # If any errors occur, a string consisting of descriptions of the errors
  1811. # separated by newlines is returned.  In no case will the string start with a
  1812. # numeric value.  If no errors occur,  the number of lines read is returned.
  1813. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  1814. FlagsOK,
  1815. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  1816.     if (Comment != "")
  1817.     Comment = "^" Comment
  1818.     AssignLen = length(Assign)
  1819.     if (VarPat == "")
  1820.     VarPat = "."    # null varname not allowed
  1821.     while ((Status = (getline Line < File)) == 1) {
  1822.     LineNum++
  1823.     sub("^[ \t]+","",Line)
  1824.     if (Line == "")        # blank line
  1825.         continue
  1826.     if (Comment != "" && Line ~ Comment)
  1827.         continue
  1828.     if (Pos = index(Line,Assign)) {
  1829.         Var = substr(Line,1,Pos-1)
  1830.         Val = substr(Line,Pos+AssignLen)
  1831.         if (StripWhite) {
  1832.         sub("^[ \t]+","",Val)
  1833.         sub("[ \t]+$","",Val)
  1834.         }
  1835.     }
  1836.     else {
  1837.         Var = Line    # If no value, var is entire line
  1838.         Val = ""
  1839.     }
  1840.     if (!FlagsOK && Val == "") {
  1841.         Errs = Errs \
  1842.         sprintf("\nBad assignment on line %d of file %s: %s",
  1843.         LineNum,File,Line)
  1844.         continue
  1845.     }
  1846.     sub("[ \t]+$","",Var)
  1847.     if (Var !~ VarPat) {
  1848.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  1849.         LineNum,File,Var)
  1850.         continue
  1851.     }
  1852.     if (!(Var in Lines)) {
  1853.         Lines[Var] = LineNum
  1854.         if (Pos)
  1855.         Values[Var] = Val
  1856.     }
  1857.     }
  1858.     if (Status)
  1859.     Errs = Errs "\nCould not read file " File
  1860.     close(File)
  1861.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  1862. }
  1863.  
  1864. # Variables:
  1865. # Data is stored in Options[].
  1866. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  1867. # Global vars:
  1868. # Sets OptErr.  Uses ENVIRON[].
  1869. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  1870. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  1871. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  1872. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  1873.     split("",filesRead,"")    # make awk know this is an array
  1874.     NumVars = split(VarNames,Vars,",")
  1875.     TypesInd = Ret = 0
  1876.     if (EnvSearch == -1)
  1877.     EnvSearch = NumVars
  1878.     for (i = 1; i <= NumVars; i++) {
  1879.     Var = Vars[i]
  1880.     CharOpt = substr(OptList,++TypesInd,1)
  1881.     if (CharOpt ~ "^[:;*()#<>&]$")
  1882.         CharOpt = substr(OptList,++TypesInd,1)
  1883.     Map[Var] = CharOpt
  1884.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1885.     # Do not overwrite entries from environment
  1886.     if (i <= EnvSearch && Var in ENVIRON &&
  1887.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,"e")) < 0)
  1888.         return Err
  1889.     }
  1890.  
  1891.     numrcFiles = split(rcFiles,fNames,":")
  1892.     for (i = 1; i <= numrcFiles; i++) {
  1893.     rcFile = fNames[i]
  1894.     if (rcFile ~ "^~/")
  1895.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1896.     else if (rcFile ~ /^\$/) {
  1897.         rcFile = substr(rcFile,2)
  1898.         match(rcFile,"^[a-zA-Z0-9_]*")
  1899.         envvar = substr(rcFile,1,RLENGTH)
  1900.         if (envvar in ENVIRON)
  1901.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1902.         else
  1903.         continue
  1904.     }
  1905.     if (rcFile in filesRead)
  1906.         continue
  1907.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1908.     # may be the same
  1909.     filesRead[rcFile]
  1910.     if ("x" in Options)
  1911.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1912.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1913.     if (retStr > 0)
  1914.         READ_RCFILE = 1
  1915.     else if (ret != "") {
  1916.         OptErr = retStr
  1917.         Ret = -1
  1918.     }
  1919.     for (Var in Lines)
  1920.         if (Var in Map) {
  1921.         if ((Err = AssignVal(Map[Var],Var in Values ? Values[Var] : "",
  1922.         Options,Types[Var],Var in Values,Var,"f")) < 0)
  1923.             return Err
  1924.         }
  1925.         else {
  1926.         OptErr = sprintf(\
  1927.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1928.         Lines[Var],rcFile)
  1929.         Ret = -1
  1930.         }
  1931.     }
  1932.  
  1933.     if ("x" in Options)
  1934.     for (Var in Map)
  1935.         if (Map[Var] in Options)
  1936.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1937.         "/dev/stderr"
  1938.         else
  1939.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1940.     return Ret
  1941. }
  1942.  
  1943. # OptSets is a semicolon-separated list of sets of option sets.
  1944. # Within a list of option sets, the option sets are separated by commas.  For
  1945. # each set of sets, if any option in one of the sets is in Options[] AND any
  1946. # option in one of the other sets is in Options[], an error string is returned.
  1947. # If no conflicts are found, nothing is returned.
  1948. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1949. # the exclusions presented by the first set of sets (ab,def,g) if:
  1950. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1951. # (a or b is in Options[]) AND (g is in Options) OR
  1952. # (d, e, or f is in Options[]) AND (g is in Options)
  1953. # An error will be returned due to the exclusions presented by the second set
  1954. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1955. # todo: make options given on command line unset options given in config file
  1956. # todo: that they conflict with.
  1957. function ExclusiveOptions(OptSets,Options,
  1958. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1959. SetNum,OSetNum) {
  1960.     NumSetSets = split(OptSets,SetSets,";")
  1961.     # For each set of sets...
  1962.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1963.     # NumSets is the number of sets in this set of sets.
  1964.     NumSets = split(SetSets[SetSet],Sets,",")
  1965.     # For each set in a set of sets except the last...
  1966.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1967.         s1 = Sets[SetNum]
  1968.         L1 = length(s1)
  1969.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1970.         # If any of the options in this set was given, check whether
  1971.         # any of the options in the other sets was given.  Only check
  1972.         # later sets since earlier sets will have already been checked
  1973.         # against this set.
  1974.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1975.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1976.             s2 = Sets[OSetNum]
  1977.             L2 = length(s2)
  1978.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1979.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1980.                 ErrStr = ErrStr "\n"\
  1981.                 sprintf("Cannot give both %s and %s options.",
  1982.                 c1,c2)
  1983.             }
  1984.     }
  1985.     }
  1986.     if (ErrStr != "")
  1987.     return substr(ErrStr,2)
  1988.     return ""
  1989. }
  1990.  
  1991. # The value of each instance of option Opt that occurs in Options[] is made an
  1992. # index of Set[].
  1993. # The return value is the number of instances of Opt in Options.
  1994. function Opt2Set(Options,Opt,Set,  count) {
  1995.     if (!(Opt in Options))
  1996.     return 0
  1997.     Set[Options[Opt]]
  1998.     count = Options[Opt,"count"]
  1999.     for (; count > 1; count--)
  2000.     Set[Options[Opt,count]]
  2001.     return count
  2002. }
  2003.  
  2004. # The value of each instance of option Opt that occurs in Options[] that
  2005. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  2006. # Other values are made indexes of Set[].
  2007. # The return value is the number of instances of Opt in Options.
  2008. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  2009.     ret = Opt2Set(Options,Opt,aSet)
  2010.     for (value in aSet)
  2011.     if (substr(value,1,1) == "!")
  2012.         nSet[substr(value,2)]
  2013.     else
  2014.         Set[value]
  2015.     return ret
  2016. }
  2017.  
  2018. # Returns true if any option in the string Opts was given, as indicated by the
  2019. # data in Options[]. If any of Arg, Env, or File are true, the given opts are
  2020. # only considered to have been set if they were set in the command line
  2021. # arguments, environment, or in a configuration file, respectively. 
  2022. function OptsGiven(Options,Opts,Arg,Env,File,  l,i,Opt,j,c) {
  2023.     if (!Arg && !Env && !File)
  2024.     Arg = Env = File = 1
  2025.     l = length(Opts)
  2026.     for (i = 1; i <= l; i++) {
  2027.     Opt = substr(Opts,i,1)
  2028.     for (j = 1; (Opt,"num",j) in Options; j++) {
  2029.         c = Options[Opt,"num",j]
  2030.         if (Arg && c+0 > 0 || File && c == "f" || Env && c == "e")
  2031.         return 1
  2032.     }
  2033.     }
  2034.     return 0
  2035. }
  2036. ### End of ProcArgs library
  2037.